package com.pcdotfan.zhengfang.controller;

import java.net.URI;
import java.util.List;
import java.util.Optional;

import com.kosprov.jargon2.api.Jargon2;
import com.pcdotfan.zhengfang.exception.AuthenticationFailedException;
import com.pcdotfan.zhengfang.exception.NotFoundException;
import com.pcdotfan.zhengfang.model.User;
import com.pcdotfan.zhengfang.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import static com.kosprov.jargon2.api.Jargon2.jargon2Hasher;
import static com.kosprov.jargon2.api.Jargon2.jargon2Verifier;

@RestController
@CrossOrigin
@RequestMapping("/api")
public class UserController {

	@Autowired
	private UserRepository userRepository;

	@GetMapping("/users")
	public List<User> retrieveAllStudents() {
		return userRepository.findAll();
	}

	@GetMapping("/users/{id}")
	public User retrieveUser(@PathVariable long id) {
		Optional<User> user = userRepository.findById(id);

		if (!user.isPresent())
			throw new NotFoundException("id-" + id);

		return user.get();
	}

	@DeleteMapping("/users/{id}")
	public void deleteUser(@PathVariable long id) {
		userRepository.deleteById(id);
	}

	@PostMapping("/users")
	public ResponseEntity<Object> createStudent(@RequestBody User reqUser) {
		byte[] password = reqUser.getPassword().getBytes();

		Jargon2.Hasher hasher = jargon2Hasher()
				.type(Jargon2.Type.ARGON2d)
				.memoryCost(65536)
				.timeCost(3)
				.parallelism(4)
				.saltLength(16)
				.hashLength(16);

		// Set the password and calculate the encoded hash
		User user = new User();
		user.setUsername(reqUser.getUsername());
		user.setAvatar(reqUser.getAvatar());
		user.setNickname(reqUser.getNickname());
		user.setPassword(hasher.password(password).encodedHash());
		User savedUser = userRepository.save(user);

		URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
				.buildAndExpand(savedUser.getId()).toUri();

		return ResponseEntity.created(location).build();
	}

	@PutMapping("/users/{id}")
	public ResponseEntity<Object> updateUser(@RequestBody User user, @PathVariable long id) {

		Optional<User> userOptional = userRepository.findById(id);

		if (!userOptional.isPresent())
			return ResponseEntity.notFound().build();

		user.setId(id);
		userRepository.save(user);

		return ResponseEntity.noContent().build();
	}

	@PostMapping("/auth")
	public boolean authenticate(@RequestBody AuthForm authForm) {
		Optional<User> userOptional = userRepository.findByUsername(authForm.getUsername());

		if (!userOptional.isPresent())
			return false;

		// verify form password
		byte[] password = authForm.getPassword().getBytes();

		Jargon2.Verifier verifier = jargon2Verifier();

		return verifier.hash(userOptional.get().getPassword()).password(password).verifyEncoded();
	}

	@PostMapping("/change-password")
	public boolean changePassword(@RequestBody ChangePasswordForm cpForm) {
		boolean match = authenticate(new AuthForm(cpForm.getUsername(), cpForm.getOldPassword()));
		Jargon2.Hasher hasher = jargon2Hasher()
				.type(Jargon2.Type.ARGON2d)
				.memoryCost(65536)
				.timeCost(3)
				.parallelism(4)
				.saltLength(16)
				.hashLength(16);

		if (match) {
			Optional<User> userOptional = userRepository.findByUsername(cpForm.getUsername());

			if (!userOptional.isPresent())
				return false;

			User user = userOptional.get();
			byte[] password = cpForm.getNewPassword().getBytes();

			user.setPassword(hasher.password(password).encodedHash());
			userRepository.save(user);
			return true;
		}
		return false;
	}
}

class AuthForm {
	private String username;
	private String password;

	AuthForm() {

	}

	AuthForm(String username, String password) {
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return this.username;
	}
	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return this.password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}

class ChangePasswordForm {
	private String username;
	private String oldPassword;
	private String newPassword;

	ChangePasswordForm() {

	}

	ChangePasswordForm(String username, String oldPassword, String newPassword) {
		this.username = username;
		this.oldPassword = oldPassword;
		this.newPassword = newPassword;
	}

	public String getUsername() {
		return this.username;
	}
	public void setUsername(String username) {
		this.username = username;
	}

	public String getOldPassword() {
		return this.oldPassword;
	}
	public void setOldPassword(String oldPassword) {
		this.oldPassword = oldPassword;
	}

	public String getNewPassword() {
		return this.newPassword;
	}
	public void setNewPassword(String newPassword) {
		this.newPassword = newPassword;
	}
}
